/**
* @file line.cpp
* August 1, 2008
* 
* @brief Implementation of the LINE, a specialized curve object.
*
* This class derives the interface "Curve", implementing all the
* specific features of a LINE geometric object.
* 
* @author Antonio Miranda (amiranda@tecgraf.puc-rio.br )
* @author Andre Pereira (brabo@tecgraf.puc-rio.br)
* 
* @see Curve
* @see CurveFactory
*/

// includes
//----------
#include "line.h"
#include "polyline.h"
#include "math.h"

#include <string.h>
#include <stdio.h>

//
/////////////////////////////////////////////////////////////////////
Line::Line()
: m_numPoints( 0 )
{
  m_type = LINE;
}


//
/////////////////////////////////////////////////////////////////////
Line::~Line()
{
}


//
/////////////////////////////////////////////////////////////////////
void 
Line::getName( char *name )
{
  strcpy (name, "line");
}

//
/////////////////////////////////////////////////////////////////////
vector<Point3D>
Line::getCurvePoints()
{
  vector<Point3D> points(2);
  points[0] = m_p0;
  points[1] = m_p1;
  return points;
}


//
/////////////////////////////////////////////////////////////////////
void
Line::getSubdvision( int nd,
                     double ratio,
                     vector<Point3D>& subdp )
{
  subdp.resize( nd+1 );

  // parametric subdivision
  vector<double> parSubd;
  getParamSubdivion( nd, ratio, parSubd );

  // begin point
  subdp[0] = m_p0;

  // intermediary points
  for( int i = 1; i < nd; ++i )
    subdp[i] = m_p0 + parSubd[i] * (m_p1 - m_p0);

  // end point
  subdp[nd] = m_p1;
}


//
/////////////////////////////////////////////////////////////////////
void
Line::getTangent( int nd,
                  double ratio,
                  vector<Point3D>& tang )
{
  tang.resize( nd+1 );

  // only one tangent
  Point3D localTang = m_p1 - m_p0;
  localTang.normalize();

  // intermediary points
  for( int i = 0; i <= nd; ++i )
    tang[i] = localTang;
}


//
/////////////////////////////////////////////////////////////////////
double
Line::getLenght()
{
  return m_p0.distance(m_p1);
}


// WHY DO WE NEED TOLERANCE HERE???? 
/////////////////////////////////////////////////////////////////////
int
Line::clstPoint( Point3D pt,
                 double& t,
                 Point3D& pt_on,
                 double tol )
{
  double d;  // distance to the closest point on the segment

  if( !distancePoint2Segment( m_p0, m_p1, pt, pt_on, d ) )
    return 0;

  // length of line
  double len = m_p0.distance( m_p1 );
  if( len == 0 )
    return 0;

  // computes the curve parameter where lies the closest point
  t = pt_on.distance( m_p0 ) / len;

  return 1;
}


//
/////////////////////////////////////////////////////////////////////
void
Line::get3dPoint( double t,
                  Point3D& pt,
                  Point3D& tg )
{
  pt = m_p0 + t * (m_p1 - m_p0);
  tg = m_p1 - m_p0;
}


//
/////////////////////////////////////////////////////////////////////
Point3D
Line::get3dPoint( double _t )
{
  return (m_p0 + _t * (m_p1 - m_p0));
}


//
/////////////////////////////////////////////////////////////////////
void
Line::draw()
{
  if( m_pDrawObj == NULL )
    return;

  if( !this->possible() )
    return;

  m_pDrawObj->begin();

  m_pDrawObj->addVertex( m_p0.getX(), m_p0.getY(), m_p0.getZ() );
  m_pDrawObj->addVertex( m_p1.getX(), m_p1.getY(), m_p1.getZ() );

  m_pDrawObj->end();
}


//
/////////////////////////////////////////////////////////////////////
void
Line::setBox()
{
  m_box.clear();
  m_box.update( m_p0.getX(), m_p0.getY(), m_p0.getZ() ); 
  m_box.update( m_p1.getX(), m_p1.getY(), m_p1.getZ() ); 
}

//
/////////////////////////////////////////////////////////////////////
int
Line::beginAddPoint( const vector<Point3D>& _addp )
{
  return 1;
}


//
/////////////////////////////////////////////////////////////////////
int
Line::addPoint( Point3D& _pt )
{
  // gambiarra para poder desenhar as varias linhas
  if (m_numPoints == 2)
    m_numPoints = 0;

  if (m_numPoints == 0)
  {
    m_p0 = _pt;
    ++m_numPoints;
    m_firstDraw = true;
    return 1;
  }
  else if (m_numPoints == 1)
  {
    // It is not allowed to coincide the points in Lines:
    if( m_p0.distance(_pt) == 0.0 )
      return 0;
    m_p1 = _pt;
    ++m_numPoints;
    m_firstDraw = true;
    return 1;
  }
  return 0;
}


//
/////////////////////////////////////////////////////////////////////
int
Line::addPoint( Point3D& _pt,
                double tol )
{
  // gambiarra para poder desenhar as varias linhas
  if (m_numPoints == 2)
   m_numPoints = 0;

  if (m_numPoints == 0)
  {
    m_p0 = _pt;
    ++m_numPoints;
    m_firstDraw = true;
    return 1;
  }
  else if (m_numPoints == 1)
  {
    // It is not allowed to coincide the points in Lines:
    if( m_p0.distance(_pt) < tol )
      return 0;
    m_p1 = _pt;
    ++m_numPoints;
    m_firstDraw = true;
    return 1;
  }
  return 0;
}


//
/////////////////////////////////////////////////////////////////////
void
Line::addTempPoint( Point3D& pt )
{
  if (m_pDrawObj == NULL)
    return;
  if (m_numPoints != 1)
    return;

  int j;
  for (j = 0; j < 2; j++)
  {
    m_pDrawObj->setXor(1);

    if (m_firstDraw)
    {
      draw(pt);
      m_lastPt = pt;
      m_firstDraw = false;
      m_pDrawObj->end();
      m_pDrawObj->setXor(0);
      break;
    }
    else
    {
      if (j == 0)
      {
        draw (m_lastPt);
      }
      else
      {
        draw (pt);
        m_lastPt = pt;
      }
    }
    m_pDrawObj->setXor(0);
  }
}

//
/////////////////////////////////////////////////////////////////////
void
Line::addTempOrthoPoint( Point3D& pt )
{
  if (m_pDrawObj == NULL)
    return;
  if (m_numPoints != 1)
    return;


  int j;
  for (j = 0; j < 2; j++)
  {
    m_pDrawObj->setXor(1);

    if (m_firstDraw)
    {
      draw(pt);
      m_lastPt = pt;
      m_firstDraw = false;
      m_pDrawObj->end();
      m_pDrawObj->setXor(0);
      break;
    }
    else
    {
      if (j == 0)
      {
        draw (m_lastPt);
      }
      else
      {
        Point3D pt_diff = pt - m_p0;

        double abs_x = fabs(pt_diff.getX());
        double abs_y = fabs(pt_diff.getY());
        double abs_z = fabs(pt_diff.getZ());

        if ( (abs_x > abs_y) && (abs_x > abs_z) )
        {
          pt_diff.setY(0);
          pt_diff.setZ(0);
        }
        else if ( abs_y > abs_z )
        {
          pt_diff.setX(0);
          pt_diff.setZ(0);
        }
        else
        {
          pt_diff.setX(0);
          pt_diff.setY(0);
        }
        pt = m_p0 + pt_diff;

        draw(pt);
        m_lastPt = pt;
      }
    }
    m_pDrawObj->setXor(0);
  }
}

//
/////////////////////////////////////////////////////////////////////
Point3D
Line::getLastTempPoint()
{ 
  return m_lastPt;
}

//
/////////////////////////////////////////////////////////////////////
int
Line::endAddPoint()
{ 
  if( !possible() )
    return 0;
  setBox();
  return 1;
}

/////////////////////////////////////////////////////////////////////
void
Line::getEndPoints( Point3D pt[2] )
{
  pt[0] = m_p0;
  pt[1] = m_p1;
}

//
/////////////////////////////////////////////////////////////////////
void
Line::delPoint( Point3D& pt )
{
  --m_numPoints;
}


//
/////////////////////////////////////////////////////////////////////
void
Line::delPoint( Point3D& pt,
                double tol )
{
  if (pt.distance(m_p1) < tol)
  {
    --m_numPoints;
  }
  else if (pt.distance(m_p0) < tol)
  {
    m_p0 = m_p1;
    --m_numPoints;
  }
}


//
/////////////////////////////////////////////////////////////////////
void
Line::copy( Curve** cp )
{
  *cp = new Line;
  Line *subobj = (Line *) *cp;
  subobj->m_p0 = this->m_p0;
  subobj->m_p1 = this->m_p1;
  subobj->m_numPoints = this->m_numPoints;
  subobj->m_type = this->m_type;
  subobj->m_subdv = this->m_subdv;
  subobj->m_ratio = this->m_ratio;
  subobj->m_attr = this->m_attr;
  subobj->m_box = this->m_box;
}


//
/////////////////////////////////////////////////////////////////////
void
Line::transform( double m[4][4] )
{
  Point3D a = m_p0;
  double w = m[3][0]*a.getX()+m[3][1]*a.getY()+m[3][2]*a.getZ()+m[3][3];
  m_p0.setX ((m[0][0]*a.getX() + m[0][1]*a.getY() + m[0][2]*a.getZ() + m[0][3]) / w);
  m_p0.setY ((m[1][0]*a.getX() + m[1][1]*a.getY() + m[1][2]*a.getZ() + m[1][3]) / w);
  m_p0.setZ ((m[2][0]*a.getX() + m[2][1]*a.getY() + m[2][2]*a.getZ() + m[2][3]) / w);

  a = m_p1;
  w = m[3][0]*a.getX()+m[3][1]*a.getY()+m[3][2]*a.getZ()+m[3][3];
  m_p1.setX ((m[0][0]*a.getX() + m[0][1]*a.getY() + m[0][2]*a.getZ() + m[0][3]) / w);
  m_p1.setY ((m[1][0]*a.getX() + m[1][1]*a.getY() + m[1][2]*a.getZ() + m[1][3]) / w);
  m_p1.setZ ((m[2][0]*a.getX() + m[2][1]*a.getY() + m[2][2]*a.getZ() + m[2][3]) / w);
}


//
/////////////////////////////////////////////////////////////////////
void
Line::invert()
{
  Point3D tmp = m_p0;
  m_p0 = m_p1;
  m_p1 = tmp;
}

//
/////////////////////////////////////////////////////////////////////
int
Line::possible()
{
  if (m_numPoints == 2)
    return 1;
  return 0;
}

//
/////////////////////////////////////////////////////////////////////
void
Line::print()
{
  std::cout << " - Line" << std::endl;
  std::cout << "   p0: ( "<<m_p0.getX()<<"; "<<m_p0.getY()<<"; "<<m_p0.getZ()<<" )"<< std::endl;
  std::cout << "   p1: ( "<<m_p1.getX()<<"; "<<m_p1.getY()<<"; "<<m_p1.getZ()<<" )"<< std::endl;
}


//
/////////////////////////////////////////////////////////////////////
int
Line::planeIntersect( Point3D& _n,
                     double _d,
                     vector<Curve*>& _curves,
                     vector<Point3D>& _pts)
{
  // using algorithm: O'Rourke - Computational Geometry in C
  // Code 7.7 - pg. 231

  // return statements:
  // 0 : the segment lies strictly to one side or the other of the plane.
  // 1 : the segment intersects the plane, and none of {2,3,4} holds.
  // 2 : the segment lies wholly within the plane.
  // 3 : the (first) m_p0 endpoint is on the plane (but not m_p1).
  // 4 : the (second) m_p1 endpoint is on the plane (but not m_p0).
  
  _curves.clear();
  _n.normalize();
  double t;  // parametric value from 0 to 1
  double num = -1.0*_d - m_p0.dotProd( _n );
  double denom = _n.dotProd( m_p1 - m_p0 );

  if( denom == 0.0 ) // segment is parallel to plane
  {
    if( num == 0.0 ) // m_p0 is on plane
      return 2;
    else
      return 0;
  }
  else
    t = num / denom;  // parametric value (0-1) where intersection occurs

  if( (0.0 < t) && (t < 1.0) )
  {
    // computes the intersection point
    Point3D newPt( m_p0 + t * ( m_p1 - m_p0 ) );
    // creates the two new curves
    Curve* line_1 = new Line();
    Curve* line_2 = new Line();
    // defining the LINEs with two points
    line_1->addPoint( m_p0 );
    line_1->addPoint( newPt );
    line_2->addPoint( newPt );
    line_2->addPoint( m_p1 );
    // obj to be returned with the new curves created due to the intersection
    _curves.push_back( line_1 );
    _curves.push_back( line_2 );

    // add point of intersection
    _pts.push_back(newPt);

    return 1;
  }
  else if( num == 0.0 )  // t == 0
    return 3;
  else if( num == denom )  // t == 1
    return 4;
  else
    return 0;
}

//
/////////////////////////////////////////////////////////////////////
int
Line::planeIntersect( Point3D& _orig,
                     Point3D& _n,
                     vector<Curve*>& _curves,
                     vector<Point3D>& _pts)
{
  // finds the minimal distance from origin to the plane
  _n.normalize();
  double d = _orig.dotProd( _n );
  return planeIntersect( _n, d, _curves, _pts);
}

//
/////////////////////////////////////////////////////////////////////
int
Line::join( Curve* _givenCurve,
            Curve** _thirdCurve )
{
  //---------------------------------------------------
  // IMPLEMENTATIONs for the FUTURE: it should return a JOINED curve
  //---------------------------------------------------
  if( (_givenCurve->getType() == Curve::LINE)  )
  {
    double tol = 1.0e-5;
    PolyLine* newCurve = new PolyLine;
    Line* givenCurve = (Line *)_givenCurve;
    // tests if an endpoint has the same coordinates of a start point of the other
    if( m_p0.isAtSamePosition( givenCurve->m_p0, tol ) )
    {
      newCurve->addPoint( m_p1 );
      newCurve->addPoint( m_p0 );
      newCurve->addPoint( givenCurve->m_p1 );
    }
    else if( m_p0.isAtSamePosition( givenCurve->m_p1, tol ) )
    {
      newCurve->addPoint( m_p1 );
      newCurve->addPoint( m_p0 );
      newCurve->addPoint( givenCurve->m_p0 );
    }
    else if( m_p1.isAtSamePosition( givenCurve->m_p0, tol ) )
    {
      newCurve->addPoint( m_p0 );
      newCurve->addPoint( m_p1 );
      newCurve->addPoint( givenCurve->m_p1 );
    }
    else if( m_p1.isAtSamePosition( givenCurve->m_p1, tol ) )
    {
      newCurve->addPoint( m_p0 );
      newCurve->addPoint( m_p1 );
      newCurve->addPoint( givenCurve->m_p0 );
    }
    newCurve->endAddPoint();
    *_thirdCurve = newCurve;
    return 1;
  }
  return 0;
}

// TODO: checks return statement, ONLY SPLIT if CURVE has 2 points?
/////////////////////////////////////////////////////////////////////
int
Line::split( vector<Curve*>& _curves )
{
  split( m_subdv, m_ratio, _curves );
  return 1;
}

// TODO: checks return statement, ONLY SPLIT if CURVE has 2 points?
/////////////////////////////////////////////////////////////////////
int
Line::split( int _subdvision,
             double _ratio,
             vector<Curve*>& _curves )
{
  // get the subdivision points for the curve
  vector<Point3D> subv;
  getSubdvision( _subdvision, _ratio, subv );
  
  // loop over the number of subdivision points to build the new curves
  int size = (int)subv.size();

  for( int i=1; i<size; i++ )
  {
    // creates a new curve (LINE)
    Curve* curve = new Line;

    // adds a pair of subdivision points to the curve
    curve->addPoint( subv[i-1] );
    curve->addPoint( subv[i] );

    // adds the new curve to the list
    _curves.push_back( curve );
  }

  return 1;
}

// TODO: checks return statement, ONLY SPLIT if CURVE is completed?
/////////////////////////////////////////////////////////////////////
Curve* 
Line::split( double _t0, double _t1, double _tol )
{
  Curve* line = NULL;

  // if the interval is from 0 to 1 then it creates a copy of the curve
  if( (_t0==0.0) && (_t1==1.0) )
  {
      this->copy( &line );
  }
  else
  {
    // creates a new curve (LINE)
    line = new Line;

    // adds a pair of subdivision points to the curve
    Point3D _pts = get3dPoint(_t0);
    line->addPoint( _pts );
    _pts = get3dPoint(_t1);
    line->addPoint( _pts );
  }

  return line;
}

//
/////////////////////////////////////////////////////////////////////
void
Line::draw( Point3D& pt )
{
  static char text[50];

  if (m_numPoints != 1)
    return;

  // draw line
  m_pDrawObj->begin();
  {
    m_pDrawObj->addVertex( m_p0.getX(), m_p0.getY(), m_p0.getZ() );
    m_pDrawObj->addVertex(   pt.getX(),   pt.getY(),   pt.getZ() );
  }
  m_pDrawObj->end();

  // draw text update length
  sprintf(text, "l=%.3f", m_p0.distance(pt));
  Point3D c = (m_p0 + pt) / 2.0;
  m_pDrawObj->text(c.getX(), c.getY(), c.getZ(), text);

}


//
/////////////////////////////////////////////////////////////////////
void
Line::updateData( int dg, int nP, int nU, double* P, double* U )
{
  if( nP!=2 )
    printf("WARNING: Line::updateData(...) -> it should not be a LINE!\n");
  // setting the read data in the curve variables:
  m_p0.setXYZ(P[0], P[1], P[2]);
  m_p1.setXYZ(P[4*(nP-1)], P[4*(nP-1)+1], P[4*(nP-1)+2]);
  m_numPoints = 2;
}
